package com.ruoyi.framework.mybatisplus.interceptor;

import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.text.StrFormatter;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.reflect.ReflectUtils;
import com.ruoyi.framework.mybatisplus.util.ControllerSqlQueueUtils;
import com.ruoyi.framework.mybatisplus.util.TypeUtil;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.util.Date;
import java.util.Map;
import java.util.regex.Matcher;

/**
 * @author yql
 * @describe ControllerSqlInterceptor
 * @date 2023/3/8 17:53
 */
public class ControllerSqlInterceptor implements InnerInterceptor {

    private static Logger log = LoggerFactory.getLogger(ControllerSqlInterceptor.class);

    public ControllerSqlInterceptor(Class clazz) {
        // TODO document why this constructor is empty
    }

    public ControllerSqlInterceptor() {

    }

    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        try {
            parseBoundSql(sh.getBoundSql());
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public String parseBoundSql(BoundSql boundSql) {
        String sql = boundSql.getSql();
        for (ParameterMapping mapping : boundSql.getParameterMappings()) {
            if (boundSql.hasAdditionalParameter(mapping.getProperty())) {
                sql = fieldTypeHandle(sql, boundSql.getAdditionalParameter(mapping.getProperty()));
            } else {
                if (boundSql.getParameterObject() instanceof Map) {
                    sql = parseBoundSqlMap(sql, mapping.getProperty(), (Map) boundSql.getParameterObject());
                } else if (TypeUtil.isBasisTypeAndString(boundSql.getParameterObject().getClass())) {
                    sql = fieldTypeHandle(sql, boundSql.getParameterObject());
                } else {
                    sql = parseBoundSqlClazz(sql, mapping.getProperty(), boundSql.getParameterObject());
                }
            }
        }
        ControllerSqlQueueUtils.appendSql(sql);
        return sql;
    }

    public String parseBoundSqlMap(String sql, String property, Map map) {
        if (property.contains(".")) {
            String[] split = property.split("\\.");
            String modelName = split[0];
            String fieldName = split[1];
            if (!map.containsKey(modelName)) {
                throw new IllegalArgumentException(String.format("parameter inexistence %s in %s", property, map));
            }
            if (map.get(modelName) instanceof BaseEntity) {
                Object value = ReflectUtils.getFieldValue(map.get(modelName), fieldName);
                if (value == null) {
                    throw new IllegalArgumentException(StrFormatter.format("class : {} not found field : {}", map.get(modelName).getClass().getSimpleName(), fieldName));
                } else {
                    return fieldTypeHandle(sql, value);
                }
            } else {
                throw new IllegalArgumentException(StrFormatter.format(" {} is not BaseEntity", modelName));
            }

        } else {
            if (!map.containsKey(property)) {
                throw new IllegalArgumentException(String.format("parameter inexistence %s in %s", property, map));
            }
            return fieldTypeHandle(sql, map.get(property));
        }
    }

    public String parseBoundSqlClazz(String sql, String property, Object obj) {
        return fieldTypeHandle(sql, ReflectUtils.getFieldValue(obj, property));
    }

    private String fieldTypeHandle(String sql, Object fieldValue) {
        if (fieldValue instanceof String) {
            sql = sql.replaceFirst("\\?", Matcher.quoteReplacement("\\'" + fieldValue + "\\'"));
        } else if (fieldValue instanceof Date) {
            sql = sql.replaceFirst("\\?", "\\'" + DateUtils.dateTime((Date) fieldValue) + "\\'");
        } else {
            sql = sql.replaceFirst("\\?", fieldValue + "");
        }
        return sql;
    }

}
